﻿using Raven.Client;
using Raven.Client.Embedded;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace FishSimulation.Model {
    public class Repository : ModelBase, IDisposable {        
        #region Members
        EmbeddableDocumentStore store;
        const long SECOND = 1000;
        const long SECOND_10 = 10000;
        const long SECOND_30 = 30000;
        const long SECOND_45 = 45000;
        const long MINUTE = 60000;
        const long MINUTE_2 = 120000;
        const long MINUTE_3 = 180000;
        const long MINUTE_5 = 300000;
        const long MINUTE_10 = 600000;
        #endregion

        #region Properties
        public static Repository Instance {
            get;
            set;
        }

        /// <summary>
        /// used by combo box in datagrid
        /// </summary>
        public ObservableCollection<ActorType> ActorTypes {
            get {
                return this.GetActorTypes();
            }
        }

        public World World {
            get;
            private set;
        }
        #endregion

        #region Constructors
        static Repository() {
            Repository.Instance = new Repository();
            Repository.Instance.Init();
        }
        
        public Repository() {
            
        }
        #endregion

        #region Methods
        public void Delete(params Actor[] actors) {
            if (actors != null && actors.Length > 0) {
                using (IDocumentSession session = store.OpenSession())
                {
                    foreach (var a in actors)
                    {
                        this.World.Actors.Remove(a);
                    }
                    session.Store(this.World);
                    session.SaveChanges();
                }
            }
        }

        public void Delete(params ActorType[] actortype)
        {
            if (actortype != null && actortype.Length > 0)
            {
                using (IDocumentSession session = store.OpenSession())
                {
                    foreach (var a in actortype)
                    {
                        Repository.Instance.ActorTypes.Remove(a);
                        var obj = session.Load<ActorType>(a.Id);
                        session.Delete(obj);
                    }                    
                    session.SaveChanges();
                }
            }
        }

        public void Dispose() {
            if (store != null)
                store.Dispose();            
        }

        /// <summary>
        /// After calling this method the ActorType property will be set to an object
        /// </summary>
        /// <param name="session"></param>
        /// <param name="actor"></param>
        public void FillActorType(IDocumentSession session, Actor actor) {
            if (actor != null) {
                actor.ActorType = GetActorType(session, actor.ActorTypeId);
            }
        }

        public ActorType GetActorType(IDocumentSession session, string id) {
            ActorType typ = null;
            
            bool dispose = false;

            try {
                if (session == null) {
                    dispose = true;
                    session = store.OpenSession();
                }
                
                if (!String.IsNullOrEmpty(id)) {
                    var list = (from t in session.Query<ActorType>()
                                where t.Id == id
                                select t).ToList();
                    if (list != null) {
                        if (list.Count > 0) {
                            typ = list[0];
                        }
                    }
                }
            }
            finally {
                if (dispose && session != null)
                    session.Dispose();
            }

            return typ;
        }

        public ObservableCollection<ActorType> GetActorTypes() {
            using (IDocumentSession session = store.OpenSession()) {
                return new ObservableCollection<ActorType>(
                    (from x in session.Query<ActorType>()
                     select x));
            }
        }
       
        private World GetWorld() {
            World w = null;
            using (IDocumentSession session = store.OpenSession()) {
                var q = (from x in session.Query<World>()
                         select x).ToList();
                if (q.Count > 0) {
                    if (q.Count > 1) throw new Exception("World count is greater than one!");
                    w = q[0];
                }
                else {
                    w = new World();
                }
            }
            return w;
        }

        public void Init() {
            if (store == null)
            {
                store = new EmbeddableDocumentStore
                {
                    DataDirectory = "FishDB"
                };
                store.Initialize();
            }
            using (IDocumentSession session = store.OpenSession()) {
                session.Advanced.MaxNumberOfRequestsPerSession = Int32.MaxValue;
                var world = Repository.Instance.GetWorld();
                
                //initialize actor types
                if (world == null || world.Pond == null || world.Actors == null || world.Actors.Count == 0) {
                    Instance.World = world;
                    world.Ticks = 30000;
                    world.Actors = new ObservableCollection<Actor>();

                    var pond = new Pond();
                    pond.Points = new ObservableCollection<Cord>();
                    pond.Points.Add(new Cord(0, 0));
                    pond.Points.Add(new Cord(800, 0));
                    pond.Points.Add(new Cord(800, 500));
                    pond.Points.Add(new Cord(0, 500));
                    world.Pond = pond;

                    var createWorldActor = new Action<ActorType, int>(
                        delegate(ActorType actorType, int n) {
                            //store the actor type
                            session.Store(actorType);

                            //create a number of the given actor type
                            for (int i = 0; i < n; i++) {
                                Actor f = Actor.Create(actorType);
                                f.ActorTypeId = actorType.Id;
                                world.Actors.Add(f);
                            }
                        }
                    );

                    //set basics like name, size, etc..
                    var blackdrum = new ActorType() { 
                          Name = "Black Drum"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.Black
                        , Description = "A white and black stripped bottom feeder."
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = MINUTE_3
                        , LifeSpanMaxTicks = MINUTE_5
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring= 3
                        , MaxLengthInches = 65                        
                        , MaxVelocity = 17
                        , WaitToMateTicks = SECOND_30
                    };
                    
                    var redfish = new ActorType() { 
                          Name = "Redfish"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.Red
                        , Description = "The fish with a spot on its tail"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = MINUTE_3
                        , LifeSpanMaxTicks = MINUTE_5
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring = 5
                        , MaxLengthInches = 65
                        , MaxVelocity = 20 
                        , Schools = true
                        , WaitToMateTicks = SECOND_30
                    };
                    
                    var trout = new ActorType() { 
                          Name = "Trout"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.Brown
                        , Description = "The fish with a lot of spots"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = SECOND_30
                        , LifeSpanMaxTicks = MINUTE
                        , MaxLengthInches = 27                        
                        , MaxVelocity = 22                        
                        , MaxNumberOfOffSpring = 5
                        , MinLengthInches = 1
                        , Schools = true
                        , WaitToMateTicks = SECOND_30
                    };
                    
                    var sheepshead = new ActorType() { 
                          Name = "Sheephead"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.White
                        , Description = "The convict fish"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = MINUTE
                        , LifeSpanMaxTicks = MINUTE_3
                        , MaxLengthInches = 32
                        , MaxNumberOfOffSpring = 2
                        , MaxVelocity = 18
                        , MinLengthInches = 1
                        , Schools = true
                        , WaitToMateTicks = SECOND_45
                    };
                    

                    var spanish = new ActorType() { 
                          Name = "Spanish Mackerel"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.Green
                        , Description = "The toothy fighter"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = SECOND_30
                        , LifeSpanMaxTicks = MINUTE
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring = 5
                        , MaxLengthInches = 40
                        , MaxVelocity = 25
                        , Schools = false
                        , WaitToMateTicks = SECOND_30
                    };
                    
                    var silverPerch = new ActorType() { 
                          Name = "Silver Perch"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.Silver
                        , Description = "A small fish with yellow fins"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = SECOND_30
                        , LifeSpanMaxTicks = MINUTE
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring = 7
                        , MaxLengthInches = 6
                        , MaxVelocity = 10
                        , Schools = true
                        , WaitToMateTicks = SECOND_30
                    };
                    
                    var mangroveSnapper = new ActorType() { 
                          Name = "Mangrove Snapper"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.Black
                        , Description = "A small fish with a big appetite"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = SECOND_30
                        , LifeSpanMaxTicks = MINUTE
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring = 10
                        , MaxLengthInches = 25
                        , MaxVelocity = 20
                        , Schools = true
                        , WaitToMateTicks = SECOND_30
                    };
                    
                    var shrimp = new ActorType() { 
                          Name = "Shrimp"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.Orange
                        , Description = "A shrimp"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = SECOND_30
                        , LifeSpanMaxTicks = MINUTE_2
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring = 15
                        , MaxLengthInches = 12
                        , MaxVelocity = 13
                        , Schools = false
                        , WaitToMateTicks = SECOND_30
                    };
                    
                    var crab = new ActorType() { 
                          Name = "Crab"
                        , CanMate = true                        
                        , Color = System.Windows.Media.Brushes.DarkBlue
                        , Description = "A crab"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = MINUTE
                        , LifeSpanMaxTicks = MINUTE_2
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring = 5
                        , MaxLengthInches = 12
                        , MaxVelocity = 10
                        , WaitToMateTicks = SECOND_30
                    };
                    
                    var muscle = new ActorType() { 
                          Name = "Muscle"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.White
                        , Description = "A muscle"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = MINUTE
                        , LifeSpanMaxTicks = MINUTE_2
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring = 3
                        , MaxLengthInches = 8
                        , MaxVelocity = 1 
                        , WaitToMateTicks = SECOND_45
                    };
                    
                    var grass = new ActorType() { 
                          Name = "Grass"
                        , CanMate = false
                        , Color = System.Windows.Media.Brushes.DarkGreen
                        , Description = "A patch of grass"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = 0
                        , LifeSpanMaxTicks = 0
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring = 1
                        , MaxLengthInches = 12                        
                        , MaxVelocity = 0 
                    };
                    
                    //set menu items
                    blackdrum.Menu = new ObservableCollection<ActorType>() { shrimp, crab, muscle };
                    redfish.Menu = new ObservableCollection<ActorType>() { trout, silverPerch, mangroveSnapper, shrimp, crab, muscle };
                    trout.Menu = new ObservableCollection<ActorType>() { trout, silverPerch, mangroveSnapper, shrimp, crab, muscle };
                    sheepshead.Menu = new ObservableCollection<ActorType>() { shrimp, crab, muscle };
                    spanish.Menu = new ObservableCollection<ActorType>() { trout, spanish, silverPerch, mangroveSnapper, shrimp, crab, muscle };
                    silverPerch.Menu = new ObservableCollection<ActorType>() { shrimp };
                    mangroveSnapper.Menu = new ObservableCollection<ActorType>() { shrimp };
                    crab.Menu = new ObservableCollection<ActorType>() { shrimp };

                    //create a number of each actor type and instances of each
                    //createWorldActor(blackdrum, 5);
                    createWorldActor(redfish, 25);
                    //createWorldActor(trout, 10);
                    //createWorldActor(sheepshead, 10);
                    //createWorldActor(spanish, 10);
                    //createWorldActor(silverPerch, 10);
                    //createWorldActor(mangroveSnapper, 10);
                    createWorldActor(shrimp, 100);
                    //createWorldActor(crab, 20);
                    //createWorldActor(muscle, 20);
                    createWorldActor(grass, 10);

                    //save the pond and all the actors in it
                    //session.Store(pond);

                    //create world actors
                    world.Pond = pond;

                    //set basic info like name, size, etc..
                    var bird = new ActorType() { 
                          Name = "Bird"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.DarkGoldenrod
                        , Description = "A bird"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = MINUTE_2
                        , LifeSpanMaxTicks = MINUTE_5
                        , MinLengthInches = 1
                        , MaxNumberOfOffSpring = 1
                        , MaxLengthInches = 30
                        , MaxVelocity = 30
                        , WaitToMateTicks = MINUTE
                    };
                    
                    var fisherman = new ActorType() { 
                          Name = "Fisherman"
                        , CanMate = true
                        , Color = System.Windows.Media.Brushes.Lime
                        , Description = "Fisherman"
                        , FoodDecrementFormula = ".25"
                        , FoodValueFormula = "10"
                        , LifeSpanMinTicks = MINUTE
                        , LifeSpanMaxTicks = MINUTE_5
                        , MinLengthInches = 60
                        , MaxNumberOfOffSpring = 1
                        , MaxLengthInches = 80
                        , MaxVelocity = 3
                        , WaitToMateTicks = MINUTE_2
                    };
                    
                    //create menu items
                    bird.Menu = new ObservableCollection<ActorType>() { blackdrum, redfish, trout, sheepshead, spanish, silverPerch, mangroveSnapper, shrimp, crab, muscle };
                    fisherman.Menu = new ObservableCollection<ActorType>() { blackdrum, redfish, trout, sheepshead, spanish, silverPerch, mangroveSnapper, crab };

                    //save actor type and create instances of each
                    //createWorldActor(bird, 5);
                    //createWorldActor(fisherman, 3);

                    session.Store(world);

                    //commit all database changes
                    session.SaveChanges();
                }

                List<ActorType> actorTypes = this.GetActorTypes().ToList();
                foreach (Actor a in world.Actors)
                {
                    a.ActorType = actorTypes.Where(x => x.Id == a.ActorTypeId).FirstOrDefault();
                }

                this.World = world;
            }
        }
        
        public void Save() {
            using (IDocumentSession session = store.OpenSession()) {
                session.Store(this.World);
                session.SaveChanges();                
            }
        }

        public void Store(ModelBase entity) {
            using (IDocumentSession session = store.OpenSession()) {
                session.Store(entity);
                session.SaveChanges();
            }
        }
        #endregion
    }
}
